from math import ceil
from re import S
from UI.Ui_MainWidget import Ui_MainWidget
from PyQt5.QtWidgets import QWidget,QMessageBox,QFileDialog,QListWidgetItem
from PyQt5.QtCore import QSettings
from PyQt5.QtGui import QIcon
from compareMusic import MusicMappingWorker
from openpyxl import Workbook
import os,time,config,subprocess,shutil
from datetime import datetime
from openpyxl.utils import get_column_letter
from UIScript.ChoosenFileItemWidget import ChoosenFileItemWidget
from extractFSB import ExtractFSB
import resource_rc
class MainWidget(QWidget):
    def __init__(self, parent=None):
        super(QWidget, self).__init__(parent)
        self.ui = Ui_MainWidget()
        self.ui.setupUi(self)
        stopMessageBox = QMessageBox(self)
        stopMessageBox.setWindowTitle("中止")
        stopMessageBox.setText("正在中止映射")
        stopMessageBox.addButton(QMessageBox.StandardButton.Ok)
        stopMessageBox.button(QMessageBox.StandardButton.Ok).hide()
        self.stopMessageBox = stopMessageBox

        finishMessageBox = QMessageBox(self)
        finishMessageBox.setWindowTitle("成功")
        okButton = finishMessageBox.addButton(QMessageBox.StandardButton.Ok)
        okButton.setText("确定")
        openButton = finishMessageBox.addButton(QMessageBox.StandardButton.Open)
        openButton.setText("打开")
        openButton.clicked.connect(self.onOpenButtonClicked)
        self.finishMessageBox = finishMessageBox
        self.choosedFilePaths = []

        self.loadSettings()
        self.resetUI()
        self.bindSignal()
        self.setWindowTitle(config.appName)
        self.setWindowIcon(QIcon(":/BgmMapper.ico"))

    def loadSettings(self):
        self.settings = QSettings(config.orgName,config.appName)
        self.ui.text_keyMusic.setText(self.settings.value(config.keyMusicDirKey,config.defaultkeyMusicDir))
        self.ui.text_valueMusic.setText(self.readValueMusicDir())

        self.ui.text_key.setText(self.settings.value("key",config.defaultKey))
        self.checkAndSetCanStartMapping()

    def bindSignal(self):
        self.ui.btn_start.clicked.connect(self.onBtnStartMappingClicked)
        self.ui.btn_stop.clicked.connect(self.onBtnStopClicked)
        self.ui.btn_keyMusic.clicked.connect(self.onBtnKeyMusicClicked)
        self.ui.btn_targetMusic.clicked.connect(self.onBtnValueMusicClicked)

        self.ui.btn_chooseFiles.clicked.connect(self.onBtnChooseFilesClicked)
        self.ui.btn_decrypt.clicked.connect(self.onBtnDecryptClicked)
        self.ui.btn_encrypt.clicked.connect(self.onBtnEncryptClicked)
        # self.ui.btn_extractOnly.clicked.connect(self.onBtnExtractOnlyClicked)
        # self.ui.Btn_DecryptAndExtract.clicked.connect(self.onBtnDecryptAndExtractClicked)


        # self.ui.btn_packOnly.clicked.connect(self.onBtnPackOnlyClicked)
        # self.ui.btn_packAndEncrypt.clicked.connect(self.onBtnPackAndEncryptClicked)
        self.ui.text_key.textChanged.connect(self.onTextKeyChanged)


    def resetUI(self):
        self.ui.progressBar_Mapping.setValue(0)
        self.ui.btn_stop.setEnabled(False)
        self.ui.group_selectFolder.setEnabled(True)
        self.checkAndSetCanStartMapping()

        # self.ui.progress_extract.setValue(0)
        self.checkAndSetCanStartExtracting()

    def onBtnDecryptClicked(self):
        dirPath = QFileDialog.getExistingDirectory(self,"选择输出目录",self.ReadDecryptOutputFolder())
        if dirPath:
            self.WriteDecryptOutputFolder(dirPath)
            for path in self.choosedFilePaths:
                result = subprocess.run([config.fsbext,"-e",self.settings.value("key",config.defaultKey),"0","-d",dirPath,path])
                if result.returncode!=0:
                    self.informError("抽取出错!")
                    return
            self.showOKOpenMessageBox(lambda:os.startfile(self.ReadDecryptOutputFolder()))


    def onBtnEncryptClicked(self):
        dirPath = QFileDialog.getExistingDirectory(self,"选择输出目录",self.ReadDecryptOutputFolder())
        if dirPath:
            self.WriteEncryptOutputFolder(dirPath)
            for path in self.choosedFilePaths:
                result = subprocess.run([config.fsbext,"-e",self.settings.value("key",config.defaultKey),"1","-d",dirPath,path])
                if result.returncode!=0:
                    self.informError("抽取出错!")
                    return
            self.showOKOpenMessageBox(lambda:os.startfile(self.ReadEncryptOutputFolder()))

    def showOKOpenMessageBox(self,openFunction):
            MessageBox = QMessageBox(self)
            MessageBox.setWindowTitle("成功")
            MessageBox.setText("任务完成")
            MessageBox.addButton(QMessageBox.StandardButton.Ok).setText("确定")
            OpenBtn = MessageBox.addButton(QMessageBox.StandardButton.Ok)
            OpenBtn.setText("打开目录")
            OpenBtn.clicked.connect(openFunction)
            MessageBox.exec()
    
    def ChooseAndEncrypt(self):
        #检查路径是否带中文，后缀是否合法
        for path in self.choosedFilePaths:
            if not path.endswith(".wav"):
                QMessageBox.critical(self,"错误","打包文件格式不支持")
                return
        
        for path in self.choosedFilePaths:
            if self.has_chinese(path):
                QMessageBox.critical(self,"错误","音乐文件路径不能带中文,请修改成英文文件名并移动到英文路径")
                return

        packOutputPath,_ = QFileDialog.getSaveFileName(self,"选择输出位置",os.path.join(self.settings.value(config.ChooseOutputDirKey,config.baseDir),"untitled"),"*.fsb")

        if len(packOutputPath) == 0:
            return
        
        if self.has_chinese(packOutputPath):
            QMessageBox.critical(self,"错误","输出路径不能带中文,请选择纯英文路径")
            return

        self.settings.setValue(config.ChooseOutputDirKey,os.path.dirname(packOutputPath))

        lstPath = os.path.join(config.tempPackDir,"temp.lst")
        if not os.path.exists(config.tempPackDir):
            os.makedirs(config.tempPackDir)

        with open(lstPath,"w",encoding="utf-8") as f:
            for path in self.choosedFilePaths:
                f.write(path+"\n")
            f.close()
        
        result = ""

        subprocess.run([config.fsbext,"-e",self.settings.value("key",config.defaultKey),"1",])

        if result.returncode!=0:
            self.informError("打包出错！")
        else:
            QMessageBox.information(self,"完成","打包完成")

    def onBtnPackAndEncryptClicked(self):
        self.ChooseFileAndPackOrEncrypt(self.settings.value("key"))

    def onTextKeyChanged(self):
        self.settings.setValue("key",self.ui.text_key.text())

    def onBtnPackOnlyClicked(self):
        self.ChooseFileAndPackOrEncrypt()
    
    def has_chinese(self,path):
        for ch in path:
            if '\u4e00' <= ch <= '\u9fff':  # 中文字符范围
                return True
        return False

    def ChooseFileAndPackOrEncrypt(self,Key=""):
        #检查路径是否带中文，后缀是否合法
        for path in self.choosedFilePaths:
            if not path.endswith(".wav"):
                QMessageBox.critical(self,"错误","打包文件格式不支持")
                return
        
        for path in self.choosedFilePaths:
            if self.has_chinese(path):
                QMessageBox.critical(self,"错误","音乐文件路径不能带中文,请修改成英文文件名并移动到英文路径")
                return

        packOutputPath,_ = QFileDialog.getSaveFileName(self,"选择输出位置",os.path.join(self.settings.value(config.ChooseOutputDirKey,config.baseDir),"untitled"),"*.fsb")

        if len(packOutputPath) == 0:
            return
        
        if self.has_chinese(packOutputPath):
            QMessageBox.critical(self,"错误","输出路径不能带中文,请选择纯英文路径")
            return

        self.settings.setValue(config.ChooseOutputDirKey,os.path.dirname(packOutputPath))

        lstPath = os.path.join(config.tempPackDir,"temp.lst")
        if not os.path.exists(config.tempPackDir):
            os.makedirs(config.tempPackDir)

        with open(lstPath,"w",encoding="utf-8") as f:
            for path in self.choosedFilePaths:
                f.write(path+"\n")
            f.close()
        
        result = ""
        if Key!="":
            result = subprocess.run([config.fsbankcl,"-o",packOutputPath,lstPath,"-encryption_key",Key])
        else:
            result = subprocess.run([config.fsbankcl,"-o",packOutputPath,lstPath])

        if result.returncode!=0:
            self.informError("打包出错！")
        else:
            QMessageBox.information(self,"完成","打包完成")

    def onBtnDecryptAndExtractClicked(self):
        # self.Decrypt()
        # self.Extract(self.getPathsInDir(config.tempDecryptDir))
        return
        

    def ReadDecryptOutputFolder(self):
        return self.settings.value(config.DecryptOutputFolderKey,config.defaultDecryptOutputFolder)
    def ReadEncryptOutputFolder(self):
        return self.settings.value(config.EncrypytOutputFolderKey,config.defaultEncryptOutputFolder)
    
    def WriteDecryptOutputFolder(self,value):
        self.settings.setValue(config.DecryptOutputFolderKey,value)

    def WriteEncryptOutputFolder(self,value):
        self.settings.setValue(config.EncrypytOutputFolderKey,value)

    def informError(self,error):
            QMessageBox.critical(self,"出错",error)

    def clearAndMakeDir(self,path):
        if os.path.exists(path):
            shutil.rmtree(path)
        os.makedirs(path)

    def getPathsInDir(self,path):
        return [os.path.join(config.tempDecryptDir,filename) for filename in  os.listdir(config.tempDecryptDir)]
    
    def onBtnExtractOnlyClicked(self):
        self.Extract(self.choosedFilePaths)

    def Extract(self,Paths):
        if not self.choosedFilePaths[0].endswith(".fsb"):
            QMessageBox.critical(self,"错误","抽取文件格式不是fsb")
            return
        self.startExtractTime = time.time()
        cpuCount = os.cpu_count()
        avgNum = ceil(len(Paths)/cpuCount)
        startIndex = 0
        self.ExtractWorkers = []
        # self.ui.progress_extract.setRange(0,len(Paths))
        self.extractedFileNum = 0
        # self.ui.progress_extract.setValue(0)
        for i in range(cpuCount):
            endIndex = min(startIndex + avgNum,len(Paths))
            if startIndex>=endIndex:
                break
            worker = ExtractFSB(startIndex,endIndex,Paths)
            startIndex+=avgNum
            worker.workerFinished.connect(self.onAnyExtractWorkerFinished)
            worker.oneFileFinished.connect(self.onAnyFileExtractFinished)
            worker.workerError.connect(self.AnyWorkerError)
            worker.start()
            self.ExtractWorkers.append(worker)
        self.ui.box_startExtract.setEnabled(False)

    def onAnyExtractWorkerFinished(self,worker):
        self.ExtractWorkers.remove(worker)
        if len(self.ExtractWorkers) ==0:
            QMessageBox.information(self,"完成",f"抽取完成,用时{ceil(time.time()-self.startExtractTime)}秒")
            self.ui.box_startExtract.setEnabled(True)

    def onAnyFileExtractFinished(self):
        self.extractedFileNum+=1
        # self.ui.progress_extract.setValue(self.extractedFileNum)
        return
    
    def onBtnChooseFilesClicked(self):
        self.choosedFilePaths,_ = QFileDialog.getOpenFileNames(self,"选择文件",self.settings.value(config.ChoosedDirKey,config.baseDir),"(*.fsb)")
        self.ui.list_choosenFiles.clear()
        for path in self.choosedFilePaths:
            item = QListWidgetItem()
            itemWidget = ChoosenFileItemWidget()
            itemWidget.setFileName(os.path.basename(path))
            self.ui.list_choosenFiles.addItem(item)
            self.ui.list_choosenFiles.setItemWidget(item,itemWidget)
        if self.choosedFilePaths:            
            lastDirPath = os.path.dirname(self.choosedFilePaths[len(self.choosedFilePaths)-1])
            self.settings.setValue(config.ChoosedDirKey,lastDirPath)
        self.checkAndSetCanStartExtracting()
            


    def onBtnStartMappingClicked(self):
        self.startMappingTime = time.time()
        keyMusicNum = 0 #源音频数
        valueMusicNum = 0   #对比音频数
        self.workers = [] 
        #多线程共享变量
        self.excelData= []
        foundedPaths = []   #已经找到音频集合         
        keyMusicPaths = []  #源路径
        valueMusicPaths = []
        for dirPath,_,fileNames in os.walk(self.settings.value(config.keyMusicDirKey,config.defaultkeyMusicDir)):
            keyMusicNum+=len(fileNames)
            for filename in fileNames:
                filePath = f"{dirPath}/{filename}"
                keyMusicPaths.append(filePath)

        for dirPath,_,fileNames in os.walk(self.readValueMusicDir()):
            valueMusicNum+=len(fileNames)
            for filename in fileNames:
                filePath = f"{dirPath}/{filename}"
                valueMusicPaths.append(filePath)

        #进度条
        self.finishedMusicNum = 0
        self.totalMusicNum = keyMusicNum 
        self.ui.progressBar_Mapping.setRange(0,self.totalMusicNum)
        self.ui.progressBar_Mapping.setValue(0)
        #分配任务
        cpuCount = os.cpu_count()
        avgNum = ceil(keyMusicNum/cpuCount)
        startIndex = 0
        for i in range(cpuCount):
            endIndex = min(startIndex + avgNum,keyMusicNum)
            if startIndex>=endIndex:
                break
            # 第一个坑：worker是局部变量，线程不是阻塞运行，他立刻被释放掉了。 报错:线程进行中被摧毁。
            worker = MusicMappingWorker(startIndex,endIndex,keyMusicPaths,valueMusicPaths,self.excelData,foundedPaths)
            worker.finishOneFile.connect(self.anyFileFinished)
            worker.workerTerminated.connect(self.anyWorkerTerminated)
            worker.workerFinished.connect(self.anyWorkerFinished)
            worker.start()
            self.workers.append(worker)
            startIndex = endIndex
        
        self.ui.btn_start.setEnabled(False)
        self.ui.btn_stop.setEnabled(True)
        self.ui.group_selectFolder.setEnabled(False)

    def onBtnStopClicked(self):
        for worker in self.workers:
            worker.stop()
        self.stopMessageBox.show()

    def onBtnKeyMusicClicked(self):
        KeyMusicDialog = QFileDialog(self)
        KeyMusicDialog.setFileMode(QFileDialog.FileMode.Directory)
        KeyMusicDialog.setOption(QFileDialog.Option.ShowDirsOnly)
        KeyMusicDialog.setDirectory(self.settings.value(config.keyMusicDirKey,config.defaultkeyMusicDir))
        if KeyMusicDialog.exec() == QFileDialog.DialogCode.Accepted:
            self.settings.setValue(config.keyMusicDirKey,KeyMusicDialog.selectedFiles()[0])
            self.ui.text_keyMusic.setText(self.settings.value(config.keyMusicDirKey))
            self.checkAndSetCanStartMapping()

    def onBtnValueMusicClicked(self):
        ValueMusicDialog = QFileDialog(self)
        ValueMusicDialog.setFileMode(QFileDialog.FileMode.Directory)
        ValueMusicDialog.setOption(QFileDialog.Option.ShowDirsOnly)
        ValueMusicDialog.setDirectory(self.readValueMusicDir())
        if ValueMusicDialog.exec() == QFileDialog.DialogCode.Accepted:
            self.writeValueMusicDir(ValueMusicDialog.selectedFiles()[0])
            self.ui.text_valueMusic.setText(self.readValueMusicDir())
            self.checkAndSetCanStartMapping()

    def checkAndSetCanStartMapping(self):
        if os.path.exists(self.settings.value(config.keyMusicDirKey)) and os .path.exists(self.readValueMusicDir()):
            self.ui.btn_start.setEnabled(True)

    def checkAndSetCanStartExtracting(self):
        canExtract = len(self.choosedFilePaths)>0
        self.ui.box_startExtract.setEnabled(canExtract)

    def onOpenButtonClicked(self):
        os.startfile(self.savedExcelPath)

    def anyFileFinished(self):
        self.finishedMusicNum+=1
        self.ui.progressBar_Mapping.setValue(self.finishedMusicNum)
        if self.finishedMusicNum == self.totalMusicNum:
            self.allFileMappingFinished()
    def anyWorkerFinished(self,worker):
        self.workers.remove(worker)

    def anyWorkerTerminated(self,worker):
        self.workers.remove(worker)
        if len(self.workers) == 0:
            self.stopMessageBox.hide()
            self.resetUI()
    def AnyWorkerError(self,worker):
        QMessageBox.critical(self,"错误","抽取出错！")

    def allFileMappingFinished(self):
        # 输出结果到Excel表格
        wb = Workbook()
        ws = wb.active
        ws.title = "BGM对照表"
        ws.append(["源音乐", "目标音乐", "相似度"])
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        self.savedExcelPath = os.path.join(config.baseDir,f"BGM对照表_{timestamp}.xlsx")
        for data in self.excelData:
            ws.append(data)

        #调整列宽
        columns_width = [100, 100, 15]  # 根据实际内容调整
        for i in range(0,len(columns_width)):
            ws.column_dimensions[get_column_letter(i+1)].width = columns_width[i] #好端端的从1开始？

        wb.save(self.savedExcelPath)

        self.finishMessageBox.setText(f"结果保存到：\n{self.savedExcelPath}\n\n用时:{round(time.time()-self.startMappingTime)}秒")
        self.finishMessageBox.show()
        self.resetUI()

    def readValueMusicDir(self):
        return self.settings.value(config.valueMusicDirKey,config.defaultValueMusicDir)
    
    def writeValueMusicDir(self,value):
        self.settings.setValue(config.valueMusicDirKey,value)

